/*
 * PPMC.cpp
 *
 *  Created on: 07/06/2010
 *      Author: administrador
 */

#include "PPMC.h"
#include "ContextoMenos1.h"
#include <stdio.h>
#include "EstadisticoNull.h"

string const PPMC::claveContextoOrdenCero = "\b";

const char* PPMC::EXTENSIONFILECOMP = ".comp";

const char* PPMC::EXTENSIONFILEDESC = ".desc";

PPMC::PPMC(int ordenMaximo, EstrategiaPPMC* estrategia, bool estadistica)
{
	this -> ordenMaximo = ordenMaximo;

	/* Inicializo la estrategia del PPMC y la clave del contexto de orden 0 */
	this -> estrategia = estrategia;

	this->caracteresLeidos = new typeCaracterChar[this->ordenMaximo+1];

	this->inicializarVariables();

	Estadistico::setOrdenMaximoPPMC(ordenMaximo);

	this->estadistico = Estadistico::getInstancia(estadistica);

}

PPMC::~PPMC()
{
	delete[] this->caracteresLeidos;
}

unsigned int PPMC::getOrdenMaximo()
{
	return this -> ordenMaximo;
}

string PPMC::obtenerClaveContexto(int ordenAEncontrar){

	string claveContexto;

	if (ordenAEncontrar == 0)
		{
			claveContexto = PPMC::claveContextoOrdenCero;
		}
		else
		{
			int maximoCaracter = 0;

			if (this->ordenMaximo < this->cantidadCaracteresLeidos){

				 maximoCaracter = this->ordenMaximo ;
			}
			else{

				maximoCaracter = this->cantidadCaracteresLeidos;
			}

			int i = maximoCaracter - ordenAEncontrar;
			while (i  < maximoCaracter)
			{
				claveContexto += this->caracteresLeidos[i];
				i++;
			}
		}

	return claveContexto;
}

void PPMC::inicializarVariables(){

	this->caracterActual = 0;

	this->CtxActual = NULL;

	this->CtxAnterior = NULL;

	this -> cantidadCaracteresLeidos = 0;

	this->ordenActual = 0;

	this->caracteresLeidos[0] = '\0';

	/* Agrego el contexto de orden 0 */

	Contexto* contextoInicial = new Contexto(claveContextoOrdenCero);

	this -> estrategia -> agregarContexto(contextoInicial);

	delete contextoInicial;

}

void PPMC::setNombreArchivoComprimir(char* rutaArchivoComprimir){

	int largoRuta = strlen(rutaArchivoComprimir);

	char* archivoComprimido = new char[largoRuta+6];

	strcpy (archivoComprimido, rutaArchivoComprimir);

	strcpy (archivoComprimido+largoRuta,PPMC::EXTENSIONFILECOMP);

	Compresor::setFile(archivoComprimido);

	delete[] archivoComprimido;

}

char* PPMC::getNombreArchivoDescomprimir(char* rutaArchivoDescomprimir){

	char* archivoPosDesc;

	archivoPosDesc = strstr(rutaArchivoDescomprimir,PPMC::EXTENSIONFILECOMP);

	int largoRuta = strlen(rutaArchivoDescomprimir);

	if (archivoPosDesc!=NULL){

		archivoPosDesc = new char[largoRuta+1];

		strncpy (archivoPosDesc,rutaArchivoDescomprimir,largoRuta-5);

		strcpy (archivoPosDesc+largoRuta-5,PPMC::EXTENSIONFILEDESC);

	}
	else{

		archivoPosDesc = new char[largoRuta+7];

		strcpy(archivoPosDesc,rutaArchivoDescomprimir);

		strcpy(archivoPosDesc+largoRuta,PPMC::EXTENSIONFILEDESC);

		}

	return archivoPosDesc;

}



int PPMC::obtenerOrdenActual()
{
	if (this -> ordenMaximo < this -> cantidadCaracteresLeidos)
		return this -> ordenMaximo;
	else
		return this -> cantidadCaracteresLeidos;
}

Contexto* PPMC::obtenerContexto()
{

	if (this->ordenActual<0){

		return new ContextoMenos1();
	}

	string claveContexto = this->obtenerClaveContexto(this->ordenActual);

	Contexto *buscado = this -> estrategia -> buscarContexto(claveContexto);

	if (!buscado){

		buscado = new Contexto(claveContexto);

		this->estrategia->agregarContexto(buscado);

	}	
	
	return buscado ;
}

void PPMC::agregarContexto()
{
	string claveContexto;

	unsigned int i;

	if (this -> ordenMaximo < this -> cantidadCaracteresLeidos)
	{
		for (i = this -> ordenMaximo - this->ordenActual; i < this -> ordenMaximo; i++)
			claveContexto += this->caracteresLeidos[i];
	}
	else
	{
		for (i = this -> cantidadCaracteresLeidos - this->ordenActual; i < this -> cantidadCaracteresLeidos; i++)
			claveContexto += this->caracteresLeidos[i];
	}

	claveContexto += this->caracterActual;



}

string PPMC::buscarClaveAnterior(string claveActual)
{
	string claveAnterior;

	claveAnterior = claveActual.substr(1,claveActual.size());

	return claveAnterior;
}

string PPMC::buscarClaveSiguiente(string claveActual)
{
//	string claveAnterior;
//	int desde;
//
//	if(claveActual == PPMC::claveContextoOrdenCero)
//	{
//		desde = CtxMaximo->getClave().size() - 1;
//
//	}else
//	{
//		desde = CtxMaximo->getClave().size() - claveActual.size() - 1 ;
//	}
//
//	claveAnterior = CtxMaximo -> getClave().substr(desde, CtxMaximo->getClave().size());
//
//	return claveAnterior;
}

Contexto* PPMC::getContextoAnterior()
{

//	string claveActual = CtxActual -> getClave();
//
//	if(CtxActual -> getOrden() == 0)
//	{
//		//TODO: deberia retornar el -1
//	}else
//	{
//		if(CtxActual -> getOrden() == 1)
//		{
//
//			return this->estrategia->buscarContexto(PPMC::claveContextoOrdenCero);
//
//		}else
//		{
//			return this -> estrategia -> buscarContexto(buscarClaveAnterior(claveActual));
//		}
//	}

	return NULL;
}

Contexto* PPMC::getContextoSiguiente()
{
	string claveActual = CtxActual -> getClave();

	if(CtxActual -> getOrden() == -1)
	{
		return this->estrategia->buscarContexto(PPMC::claveContextoOrdenCero);

	}else
	{
		return this -> estrategia -> buscarContexto(buscarClaveSiguiente(claveActual));
	}

	return NULL;
}

void PPMC::agregarCaracterLeido()
{
	if (this -> ordenMaximo <= this -> cantidadCaracteresLeidos)
	{
		for (unsigned int i = 1; i < this -> ordenMaximo; i++)
			this->caracteresLeidos[i-1] = this->caracteresLeidos[i];

		if (this->ordenMaximo!=0){

			this->caracteresLeidos[this -> ordenMaximo-1] = this->caracterActual;
			this->caracteresLeidos[this->ordenMaximo]='\0';
		}
		else{

			this->caracteresLeidos[this -> ordenMaximo] = this->caracterActual;
		}
	}
	else{
		this->caracteresLeidos[this->cantidadCaracteresLeidos] = (typeCaracterChar)this->caracterActual;
		this->caracteresLeidos[this->cantidadCaracteresLeidos+1] = '\0';
	}

	this -> cantidadCaracteresLeidos++;

}

void PPMC::comprimirCaracter(){

	bool emitidoESCAPE = true;

	while ((this->ordenActual >= -1) && (emitidoESCAPE))
			{
				this->CtxActual = this -> obtenerContexto();

				/* cout << "Orden Actual: "<< ordenActual <<"\n"; */

				emitidoESCAPE = this->CtxActual -> emitir(this->caracterActual);

				if ((emitidoESCAPE))
				{
					this->CtxActual->agregarDistribucion(this->caracterActual);
				}

				if (this->ordenActual>=0)
				{
					this->estrategia->actualizarContexto(this->CtxActual);

					// --------- Actualizo accesos del Estadistico ---------------

					estadistico -> sumarAcceso(ordenActual);

					if (!emitidoESCAPE)
					{
						// --------- Actualizo aciertos del Estadistico ---------------
						estadistico -> sumarAcierto(ordenActual);
					}
				}

				delete this->CtxActual;

				this->ordenActual--;

			}



}

void PPMC::comprimir(char* rutaArchivo)
{
	FILE* archivo;

	archivo = fopen(rutaArchivo, "rb");

	this->setNombreArchivoComprimir(rutaArchivo);

	/* Verifico que el archivo sea valido */
	if(archivo == NULL)
	{
		throw ArchivoInvalidoException(rutaArchivo);
	}

	bool fin = false;

	/* variable utilizada para saber cuantos caracteres se restan en la exclusion */

	/* cout <<"\n------------Comienza la compresion - Orden Máximo "<< this->ordenMaximo <<" --------------------------\n\n"; */

	while (!fin)
	{
		typeCaracterChar caracter;

		fread (&caracter,sizeof(typeCaracterChar),1,archivo);

		this->estadistico->sumarCaracteres();

		this->caracterActual = (typeCaracterAscii)caracter;

		if (feof(archivo)){


			this->caracterActual=FINARCH;
			fin = true;
		}
	
	/*	cout << "---------------------Comprimiendo el caracter \"" << caracterActual << "\" -- Caracteres Leidos \""<< caracteresLeidos << "\" Cantidad leidos " << this->cantidadCaracteresLeidos <<"---------------------" <<"\n\n"; */

		this->ordenActual = this ->obtenerOrdenActual();

		this->comprimirCaracter();

	/*		cout << "**********************************************************\n\n"; */

		this -> agregarCaracterLeido();

	}

	estadistico->printAciertosPorContexto();

	Compresor::destruirCompresor();

	delete this->estrategia;

	Estadistico::borrarInstancia();

	fclose(archivo);

}

void PPMC::descomprimirCaracter(){

	bool emitidoESCAPE = true;

	while (this->ordenActual>=-1 && emitidoESCAPE ){

		this->caracterActual = this->actualizarDescompresion();

/*		cout << "Caracter Emitido " << this->caracterActual << " Orden " << this->ordenActual <<'\n'; */

		if (this->caracterActual!=Distribucion::CARACTER_ESCAPE){

			emitidoESCAPE=false;
		}

		this->ordenActual--;
	}

}

void PPMC::descomprimir(char* rutaArchivo){

//	this->inicializarVariables();

	Compresor::setFile(rutaArchivo);

	FILE* archivo;

	char* archivoPosDesc = this->getNombreArchivoDescomprimir(rutaArchivo);

	archivo = fopen(archivoPosDesc, "w");

	/* Verifico que el archivo sea valido */

	if(archivo == NULL)
	{

		throw ArchivoInvalidoException(rutaArchivo);
	}

/*	cout << "comienza Descompresion" <<'\n';  */

	while (this->caracterActual !=FINARCH){

		this->ordenActual = this->obtenerOrdenActual();

		this->descomprimirCaracter();

		if (this->caracterActual!=FINARCH){

			typeCaracterChar Caracter = (typeCaracterChar)this->caracterActual;

			fwrite((void*)&Caracter,sizeof(char),sizeof(char),archivo);

			this -> agregarCaracterLeido();

/*			cout <<"Caracteres Leidos " << this->caracteresLeidos <<'\n';  */
		}


	}

	fclose(archivo);

	delete this->estrategia;

	Compresor::destruirCompresor();

	delete [] archivoPosDesc;
}

void PPMC::actualizarContextosAnteriores(typeCaracterChar caracter){

	int ordenMaximo = this->obtenerOrdenActual();

	string claveContexto;

	for (int i=ordenMaximo; i>this->ordenActual; i--){

		claveContexto = this->obtenerClaveContexto(i);

		Contexto *contextoBuscado = this->estrategia->buscarContexto(claveContexto);

		contextoBuscado->agregarDistribucion((typeCaracterChar)caracter);

		this->estrategia->actualizarContexto(contextoBuscado);

		delete contextoBuscado;
	}

}

typeCaracterAscii PPMC::actualizarDescompresion()
{

	this->CtxActual = this->obtenerContexto();

/*	cout << this->CtxActual->getClave() << '\n';  */

	typeCaracterAscii caracter = this->CtxActual->emitirDescomprimido();

	if (caracter !=Distribucion::CARACTER_ESCAPE){

		this->actualizarContextosAnteriores(caracter);
	}

	this->estrategia->actualizarContexto(this->CtxActual);

	delete this->CtxActual;

	this->CtxActual=NULL;

	return caracter;

}

//Contexto* PPMC::primerContexto()
//{
//	delete this -> CtxActual;
//
//	this -> CtxActual = this -> estrategia -> primerContexto();
//
//	return this -> CtxActual;
//}

//Contexto* PPMC::getSiguienteContexto()
//{
//	delete this -> CtxActual;
//
//	this -> CtxActual = this -> estrategia -> getSiguienteContexto(this -> CtxActual);
//
//	return this -> CtxActual;
//}
